Skip to content

fix(runtime): propagate session permissions to background agents and skills#3490

Open
Piyush0049 wants to merge 3 commits into
docker:mainfrom
Piyush0049:fix/background-agent-permissions
Open

fix(runtime): propagate session permissions to background agents and skills#3490
Piyush0049 wants to merge 3 commits into
docker:mainfrom
Piyush0049:fix/background-agent-permissions

Conversation

@Piyush0049

Copy link
Copy Markdown
Contributor

Overview

This PR resolves an issue where background agents and run_skill sub-sessions were bypassing session-level permission rules by relying on a hardcoded ToolsApproved: true flag.

Changes

  • Propagated Permissions: Added Permissions: params.ParentSession.Permissions to the SubSessionConfig in agent_delegation.go and skill_runner.go.
  • Removed Security Bypass: Removed the hardcoded ToolsApproved: true from RunAgent. Background tasks now correctly inherit both the ToolsApproved boolean and the granular Permissions rules from their parent session.

Security Impact

This is a strict security improvement. By passing the parent permissions down, the sub-sessions correctly integrate with the dispatcher's auto-deny logic:

  1. Explicitly approved tools (YOLO mode or Allow lists) run seamlessly.
  2. Tools requiring user confirmation (default Ask) hit the dispatcher's NonInteractive check and are safely auto-denied, preventing background agents from hanging or bypassing user consent.

…skills

This removes the hardcoded ToolsApproved bypass and ensures background tasks inherit the Allow/Deny/Ask permission configuration from their parent session. When tools fall back to 'Ask' in a background non-interactive context, the dispatcher correctly auto-denies them.
@Piyush0049 Piyush0049 requested a review from a team as a code owner July 6, 2026 18:04
@aheritier aheritier added kind/fix PR fixes a bug (maps to fix:). Use on PRs only. area/runtime Runtime engine, agent loop execution, tool dispatch, loop detection labels Jul 6, 2026

@dgageot dgageot left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for tackling this — inheriting session permissions instead of a blanket bypass is the right end state, and this closes a real enforcement hole where session-level allow/deny rules silently vanished in every sub-session. A few things need resolution before merge though.

Blocking

🔴 Data race: shared *PermissionsConfig pointer

pkg/runtime/agent_delegation.go:495 stores the same pointer (params.ParentSession.Permissions) on the child session. The "always allow this tool" resume path mutates sess.Permissions.Allow (pkg/runtime/toolexec/dispatcher.go:803-808) under a per-dispatcher mutex, while a background sub-session runs on a detached goroutine reading the same slices under a different dispatcher's mutex. That's a genuine data race.

Fix: deep-copy in newSubSession (or inside WithPermissions) — clonePermissionsConfig in pkg/session/branch.go:322 already does exactly this.

🔴 Unconditional back-propagation to parent

Because of the shared pointer, an "always allow tool X" approval inside a transfer/skill child mutates the parent's permissions immediately — even when the sub-session ends in error. This breaks runForwarding's success-only propagation invariant. Cloning fixes this too; if back-propagation is desired, it should be explicit on the success path.

Needs a decision

🟠 Behavior regression for run_background_agent

Before: background agents ran with ToolsApproved: true. After: in a default interactive session, any non-read-only tool call falls through to askUser, which auto-denies in non-interactive sessions (dispatcher.go:656-658) — background agents become effectively read-only unless in YOLO mode or with allow rules. The old comment described the blanket approval as a deliberate trade-off (approving run_background_agent itself was the consent gate). Is the new default the intended posture? Either way, it needs to be documented loudly.

🟠 Stale docs

docs/tools/background-agents/index.md:33 still claims "Tools run by the sub-agent are pre-approved…" — now false.

Should add

🟡 Test coverage

A security-sensitive change ships with zero test changes. Suggested additions in pkg/runtime/agent_delegation_test.go:

  • newSubSession applies cfg.Permissions (and clone isolation after the fix)
  • RunAgent inherits parent ToolsApproved/Permissions
  • RunSkillFork/handleTaskTransfer propagate Permissions

Minor

  • The if cfg.Permissions != nil guard in newSubSession becomes redundant with a nil-safe clone helper — drop it.
  • RunAgent reads params.ParentSession.ToolsApproved from a background goroutine while the parent's dispatcher may write it — pre-existing unsynchronized bool read, worth fixing while touching this code.

What's good

  • Session permission inheritance in transfer_task and skill forks is a real correctness/security win.
  • The rewritten RunAgent comment accurately describes the non-interactive auto-deny path.
  • Checker ordering (session-level first, then team) gives inherited rules correct precedence in children.

Verified on the branch: go build ./... ✅, go test ./pkg/runtime/... ./pkg/session/... ✅, go test -race ./pkg/runtime ✅, task lint ✅ (the race isn't surfaced because no test exercises the concurrent shared-permissions scenario).

@dgageot dgageot left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See feedback

@Piyush0049

Copy link
Copy Markdown
Contributor Author

Okay, I would fix this according to the feedback.

@Piyush0049

Copy link
Copy Markdown
Contributor Author

dgageot I've pushed an update addressing all the review feedback.

Summary of changes

  • Data Race and Pointer Sharing: WithPermissions now uses clonePermissionsConfig(). All concurrent access to Permissions and ToolsApproved is now routed through mutex-protected accessors (ClonePermissions(), IsToolsApproved(), SetPermissions()).
  • Back-Propagation: Updated runForwarding to explicitly back-propagate permissions only on the success path. Crucially, I ensured it uses ClonePermissions() during this back-propagation to maintain pointer isolation between the parent and child.
  • Documentation: Updated docs/tools/background-agents/index.md (and the RunAgent code comments) to loudly document the new fallback to the non-interactive auto-deny posture.
  • Test Coverage: Added all requested test cases in agent_delegation_test.go (TestNewSubSession_PermissionsIsolation, TestRunAgent_InheritsParentPermissions, TestTransferTask_PropagatesPermissions). The transfer_task integration test successfully validated the back-propagation fix.
  • Minor Polish: Removed the redundant nil guard in newSubSession and fixed the pre-existing unsynchronized bool read in RunAgent.

The tests fully run locally, and all paths are fully isolated and green.

@Piyush0049 Piyush0049 force-pushed the fix/background-agent-permissions branch from 39713be to 08fe4e6 Compare July 7, 2026 15:06
@Piyush0049

Copy link
Copy Markdown
Contributor Author

I force-pushed to fix the linting errors caught by golangci-lint (errcheck on an os.Chmod cleanup and gci import ordering). Also ensured the new commit is verified.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/runtime Runtime engine, agent loop execution, tool dispatch, loop detection kind/fix PR fixes a bug (maps to fix:). Use on PRs only.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants